tree grammar JpaTreeWalker;

options {
  language = Java;
  output = AST;
  tokenVocab = Yagdao;
  ASTLabelType = CommonTree;
 backtrack=true;
}

@header {
  package com.altuure.yagdao.grammar;
   import com.altuure.yagdao.common.YagdaoException;
   import com.altuure.yagdao.YOperator;
  import java.util.Map;
  import java.util.HashMap;
  import java.util.List;
  import java.util.LinkedList;
  import javax.persistence.criteria.*;
}


@members{
    private Root from;
    private Map<String,From> joins;
    private CriteriaBuilder cb;
    private Object value=null;
    private int defaultOp=EQUALS;
    private boolean objectReferenced;
    
    public void init(Root from, Map<String, From> joins,CriteriaBuilder criteriaBuilder){
      this.from=from;
      this.joins=joins;
      this.cb=criteriaBuilder;
    }
   
    public void setValue(Object v){
      this.value=v;
      objectReferenced=false;
    }
    public void setDefaultOp(YOperator op){
    
         switch (op) {
        case NONE: defaultOp=EQUALS; break;
        case EQUAL:defaultOp=EQUALS;break;
        case GE:defaultOp=GREATER_OR_EQ;break;
        case GT:defaultOp=GREATER;break;
        case IN:defaultOp=IN;break;
        case LE:defaultOp=LESS_OR_EQ;break;
        case LIKE:defaultOp=LIKE;break;
        case ULIKE:defaultOp=ILIKE;break;
        case NOTLIKE:defaultOp=EQUALS;break;
        case NOTULIKE:defaultOp=EQUALS;break;
        case LT:defaultOp=LESS;break;
        case BETWEEN:defaultOp=BETWEEN;break;
        case ISNULL:defaultOp=ISNULL;break;
        case ISNOTNULL:defaultOp=ISNOTNULL;break;


        } 
    }
    
    
    private List<String> errors = new LinkedList<String>();
    public void displayRecognitionError(String[] tokenNames,
                                        RecognitionException e) {
        String hdr = getErrorHeader(e);
        String msg = getErrorMessage(e, tokenNames);
        errors.add(hdr + " " + msg);
    }
    public List<String> getErrors() {
        return errors;
    }
    public Expression createExp(CommonTree tree,List args){
       List<Expression> expression=args;
        switch(tree.getType()){
            case SUM:return cb.sum(expression.get(0));          
            case MIN:return cb.min(expression.get(0));          
            case MAX:return cb.max(expression.get(0));          
            case COUNT:return cb.count(expression.get(0));          
            case AVG:return cb.avg(expression.get(0));
            case NEG:return cb.neg(expression.get(0));
            case ABS:return cb.abs(expression.get(0));
            case SQRT:return cb.sqrt(expression.get(0));
            case TOFLOAT:return cb.toFloat(expression.get(0));
            case TOINTEGER:return cb.toInteger(expression.get(0));
            case TOBIGDECIMAL:return cb.toBigDecimal(expression.get(0));
            case TOBIGINTEGER:return cb.toBigInteger(expression.get(0));
            case TOSTRING:return cb.toString(expression.get(0));
            case CONCAT:return cb.concat(expression.get(0),expression.get(1));
            case LOWER:return cb.lower(expression.get(0));
            case UPPER:return cb.upper(expression.get(0));
            case LENGTH:return cb.length(expression.get(0));
            case SUBSTRING:
              if(expression.size()==3)
                return cb.substring(expression.get(0),expression.get(1),expression.get(2));
              if(expression.size()==2)
                return cb.substring(expression.get(0),expression.get(1));
              break;
            case LOCATE:
              if(expression.size()==3)
                return cb.substring(expression.get(0),expression.get(1),expression.get(2));
              if(expression.size()==2)
                return cb.substring(expression.get(0),expression.get(1));
              break;              
            case TRIM:
              if(expression.size()==1)
                return cb.trim(expression.get(0));
              if(expression.size()==2)
                return cb.trim(expression.get(0),expression.get(1));
              break;
           case CURRENTDATE:return cb.currentDate();
           case CURRENTTIMESTAMP:return cb.currentTimestamp();
           case CURRENTTIME:return cb.currentTime();
           case SECOND:return cb.function(tokenNames[tree.getType()],Integer.class,expression.toArray(new Expression[0]));            
           case MINUTE:return cb.function(tokenNames[tree.getType()],Integer.class,expression.toArray(new Expression[0]));            
           case HOUR:return cb.function(tokenNames[tree.getType()],Integer.class,expression.toArray(new Expression[0]));            
           case DAY:return cb.function(tokenNames[tree.getType()],Integer.class,expression.toArray(new Expression[0]));            
           case MONTH:return cb.function(tokenNames[tree.getType()],Integer.class,expression.toArray(new Expression[0]));            
           case YEAR:return cb.function(tokenNames[tree.getType()],Integer.class,expression.toArray(new Expression[0]));            
           default: return cb.function(tokenNames[tree.getType()],Object.class,expression.toArray(new Expression[0]));            
            
                      
        }
        throw new YagdaoException("not implemented exception:"+tokenNames[tree.getType()]+" with args size:"+args.size());          
      
    }
     public Predicate createPredicate(boolean having,Expression exp1,CommonTree operatorTree,Expression exp2){
        if(exp2!=null){
          if(!objectReferenced &&value!=null && !Boolean.valueOf((Boolean)value))
            return null;
       }
        Predicate pre=null;
        int operator=defaultOp;
        if(operatorTree!=null)
          operator=operatorTree.getType();
        switch(operator){
            case GREATER: 
                return cb.greaterThan(exp1,getExpression(value,exp2));          
            case GREATER_OR_EQ: 
                  return cb.greaterThanOrEqualTo(exp1,getExpression(value,exp2));          
            case LESS: 
                return cb.lessThan(exp1,getExpression(value,exp2));          
            case LESS_OR_EQ: 
                return cb.lessThanOrEqualTo(exp1,getExpression(value,exp2));          
            case EQUALS:           
            case EQUALS2: return cb.equal(exp1,getExpression(value,exp2));
            case NOT_EQUALS:           
            case NOT_EQUALS2: return cb.equal(exp1,getExpression(value,exp2));
            case BETWEEN:{
              Object objs[] = (Object[]) value; 
              return cb.between(exp1,(Expression)cb.literal(objs[0]),(Expression)cb.literal(objs[1]));
          }
           case ISNULL: 
               return cb.isNull(exp1) ;               
           case ISNOTNULL: 
               return cb.isNotNull(exp1) ;             
           case ILIKE: 
               return cb.like(cb.upper(exp1),cb.upper(getExpression(value,exp2))) ;               
           case LIKE:
               return cb.like(exp1,getExpression(value,exp2)) ;               
           case IN:{
                Object objs[] = (Object[]) value; 
               CriteriaBuilder.In in=cb.in(exp1); 
               for(Object obj: objs)
                    in.value(obj);              
               return in;
            }   
            case NOT_IN:{
                Object objs[] = (Object[]) value; 
               CriteriaBuilder.In in=cb.in(exp1); 
               for(Object obj: objs)
                    in.value(obj);              
               return in.not();
            }    
            default:throw new YagdaoException("not implemented exception:"+tokenNames[operator]);          
        }

      
    }
     public Predicate joinPredicate(Predicate predicate1,CommonTree operatorTree,Predicate predicate2){

        int operator=AND;
        if(operatorTree!=null)
          operator=operatorTree.getType();
        switch(operator){
            case AND: 
                return cb.and(predicate1,predicate2);          
            case OR: 
                   return cb.or(predicate1,predicate2);          
           
            default:throw new YagdaoException("not implemented exception:"+tokenNames[operator]);          
        }

      
    }
   public Order orderProperty(Expression exp,CommonTree operatorTree){

        int operator=ASC;
        if(operatorTree!=null)
          operator=operatorTree.getType();
        switch(operator){
            case ASC: 
                return cb.asc(exp);          
            case DESC: 
                   return cb.desc(exp);          
           
            default:throw new YagdaoException("not implemented exception:"+tokenNames[operator]);          
        }

      
    }
    public Expression getExpression(Object value,Expression exp2){
        if(exp2!=null)
          return exp2;
       else
          return cb.literal(value); 
    }
}

identifier  returns [Path path,From f]:{$f=from;} (op1=ID DOT {$f=$f.join($op1.text);})* op2=ID {$path=$f.get($op2.text);} ;

result_list returns [List list,boolean distinct=false;] :(DISTINCT {$distinct=true;})? {$list=new ArrayList();}op1=result_col {$list.add($op1.exp);} (COMMA op2=result_col {$list.add($op2.exp);} )* EOF!;
result_col returns [Expression exp]: op1=expression {$exp=$op1.exp;};
condition_col returns [boolean hasHaving,Predicate predicate] 
    : op1=HAVING? op2=expression (op3=condition op4=expression?)? EOF 
    {$hasHaving=$op1!=null;$predicate=createPredicate($op1!=null,$op2.exp,$op3.tree,$op4.exp); } ;

where_condition returns [Predicate predicate] 
      :  
     (LPAREN condition_seq RPAREN) =>opX=inner_col {$predicate=$opX.predicate;}
     |op2=expression op3=condition op4=expression? {$predicate=createPredicate(false,$op2.exp,$op3.tree,$op4.exp); }
;

where_clause  returns [Predicate predicate]:op1=condition_seq {$predicate=$op1.predicate;}EOF!;

condition_seq returns [Predicate predicate]
    :op1=where_condition {$predicate=$op1.predicate;}(op=where_op op2=where_condition {$predicate=joinPredicate($predicate,op.tree,$op2.predicate);})* ;

having_col returns [Predicate predicate] 
    :op1=where_condition {$predicate=$op1.predicate;}(op=where_op op2=where_condition{$predicate=joinPredicate($predicate,op.tree,$op2.predicate);})* ;
         
group_col returns[List predicates]
      :{$predicates=new ArrayList();} op1=expression {$predicates.add($op1.exp);} (COMMA op2=expression {$predicates.add($op2.exp);} )* EOF!;    

inner_col returns [Predicate predicate] 
  : LPAREN! op1=condition_seq RPAREN! {$predicate=$op1.predicate;};

order_property returns[Order order]:  op1=expression op2=order_op? {$order=orderProperty($op1.exp,op2.tree);};
order_clause returns [List orders] :  
        op1=order_property {$orders=new ArrayList(); $orders.add($op1.order);} 
              (COMMA op2=order_property { $orders.add($op2.order);} )* EOF!;

where_op
  : AND
    |OR
  ;
order_op
  : ASC|DESC
  ;


//function returns [Expression exp] : 
//  op1=expression {$exp=op1.exp} 
//  | AVG  LPAREN! op1= expression RPAREN! {$exp=cb.avg($op1.exp);}
//  |MIN {cb.min($op1.exp);}LPAREN! op1= expression RPAREN! {$exp=cb.min($op1.exp);}
// |MAX {cb.max($op1.exp);}LPAREN! op1= expression RPAREN! {$exp=cb.max($op1.exp);}
//  |SUM{cb.sum($op1.exp);} LPAREN! op1= expression RPAREN! {$exp=cb.sum($op1.exp);}
//  ;
condition:    GREATER
              |LESS
              |EQUALS
              |EQUALS2
              |NOT_EQUALS
              |NOT_EQUALS2
              |LESS_OR_EQ
              |GREATER_OR_EQ
              |IN
              |NOT_IN
              |LIKE
              |ILIKE
              |ISNULL
              |ISNOTNULL
              |BETWEEN
              ;





invocation returns [Expression exp]
    : ( op1=functionName   LPAREN op2=argumentlist? RPAREN ){$exp=createExp($op1.tree,$op2.args);}
    ;
argumentlist returns [List args] :{$args=new ArrayList();} op1=expression {$args.add($op1.exp);}(COMMA op2=expression {$args.add($op2.exp);})* ;

term returns [Expression exp]
  : op0=identifier {$exp=$op0.path;}
  | op1=invocation {$exp=$op1.exp;}
  | LPAREN! op2=expression RPAREN! {$exp=$op2.exp;}
  | op6=QUESTION {$exp=cb.literal(value);objectReferenced=true;}
  | op3=INTEGER {$exp=cb.literal(Integer.parseInt($op3.text));}
  | op4=FLOAT {$exp=cb.literal(Double.parseDouble($op4.text));}
  | op5=STRING_LITERAL {$exp=cb.literal(($op5.text));}
  ;
  
unary returns [Expression exp,boolean negate=false]
  :{$negate=false;} (PLUS! | negation {$negate=(!$negate);})* op1=term {if($negate)$exp=cb.neg(op1.exp);else $exp=$op1.exp;} 
  ;

negation returns [Expression exp]
  : MINUS -> NEGATION
  ;

mult returns[Expression exp]
  : op1=unary {$exp=op1.exp;} (('*' {$exp=cb.prod($op1.exp,$op2.exp);} | '/' {$exp=cb.quot($op1.exp,$op2.exp);}| 'mod' {$exp=cb.mod($op1.exp,$op2.exp);}) op2=unary)*
  ;
  
expression returns [Expression exp]
  : op1=mult {$exp=op1.exp;} (('+'{$exp=cb.sum($op1.exp,$op2.exp);} | '-'{$exp=cb.diff($op1.exp,$op2.exp);}) op2=mult)*
  ;

functionName
  : 
  AVG|MIN |MAX |SUM|COUNT
  |NEG|ABS|SQRT|TOFLOAT|TOINTEGER|TOLONG|TOBIGDECIMAL|TOSTRING
  |CONCAT|SUBSTRING|TRIM|LOWER|UPPER|LENGTH|LOCATE
  |CURRENTDATE|CURRENTTIME|CURRENTTIMESTAMP
  |SECOND|MINUTE|HOUR|DAY|MONTH|YEAR
  |MOD
  |unknownFunctionName; 
unknownFunctionName: ID;     

    
